{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在，我们来动手实现 VGG16，该网络的结构图如下所示,其中每一列都表示着一种结构，从VGG11 一直到 VGG19。\n",
    "\n",
    "接下来我们就动手实现VGG16\n",
    "\n",
    "![VGG结构](../images/VGG.png)\n",
    "\n",
    "值得一说的是，VGG是我们目前碰到的第一个使用重复结构的网络，之后还会有很多很多。\n",
    "\n",
    "VGG原始论文[这里](https://arxiv.org/abs/1409.1556)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torchvision\n",
    "import torchvision.transforms as transforms\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "from torch.autograd import Variable\n",
    "from torch.utils.data import DataLoader"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "这里，我们以  CIFAR10 作为实验数据...\n",
    "\n",
    "然而，原始的VGG使用的是224 \\* 224大小的图片，然而， CIFAR10 只有32 \\* 32 所以啊，我们需要对图形进行预处理，使用 transform"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 图像预处理，因为VGG是使用224 * 224大小的图片，但是MNIST只有32 * 32, 为了能快点跑出结果，\n",
    "# 我们将它们放大到96*96，而不是原始论文的224 * 224\n",
    "transform = transforms.Compose([\n",
    "    transforms.Resize(96), # 缩放到 96 * 96 大小\n",
    "    transforms.ToTensor(),\n",
    "    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5)) # 归一化\n",
    "])\n",
    "\n",
    "# 超参数\n",
    "DOWNLOAD = True\n",
    "BATCH_SIZE = 256\n",
    "EPOCH = 5\n",
    "learning_rate = 0.001\n",
    "\n",
    "# 是否使用GPU\n",
    "use_gpu = True\n",
    "\n",
    "# 下载 CIFAR10 dataset\n",
    "train_dataset = torchvision.datasets.CIFAR10(root='./data/', train=True, transform=transform, download=DOWNLOAD)\n",
    "test_dataset = torchvision.datasets.CIFAR10(root='./data/', train=False, transform=transform)\n",
    "\n",
    "# dataloader\n",
    "\n",
    "train_loader = DataLoader(dataset=train_dataset,\n",
    "                          batch_size=BATCH_SIZE,\n",
    "                          shuffle=True)\n",
    "test_loader = DataLoader(dataset=test_dataset,\n",
    "                         batch_size=BATCH_SIZE,\n",
    "                         shuffle=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "数据处理的部分，我们结束了，接下来，我们会开始定义模型，并且训练了。\n",
    "\n",
    "注意：因为VGG是使用了重复元素的网络，所以对于conv层的定义，我们会使用一个循环。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "class VGG(nn.Module):\n",
    "    '''\n",
    "    VGG model\n",
    "    '''\n",
    "    def __init__(self, conv_features):\n",
    "        super(VGG, self).__init__()\n",
    "        self.conv_features = conv_features\n",
    "        self.classifier = nn.Sequential(\n",
    "            nn.Dropout(),\n",
    "            nn.Linear(4608, 512),\n",
    "            nn.ReLU(True),\n",
    "            nn.Dropout(),\n",
    "            nn.Linear(512, 512),\n",
    "            nn.ReLU(True),\n",
    "            nn.Linear(512, 10),\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        x = self.conv_features(x)\n",
    "        x = x.view(x.size(0), -1)\n",
    "        x = self.classifier(x)\n",
    "        return x\n",
    "\n",
    "# 构建 循环的 conv层\n",
    "def make_layers(struct, in_channels=1, batch_norm=False):\n",
    "    layers = []\n",
    "    for out_channels in struct:\n",
    "        if out_channels == 'pooling':\n",
    "            layers += [nn.MaxPool2d(kernel_size=2, stride=2)]\n",
    "        else:\n",
    "            conv2d = nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1)\n",
    "            if batch_norm:\n",
    "                layers += [conv2d, nn.BatchNorm2d(out_channels), nn.ReLU(inplace=True)]\n",
    "            else:\n",
    "                layers += [conv2d, nn.ReLU(inplace=True)]\n",
    "            in_channels = out_channels\n",
    "    return nn.Sequential(*layers)\n",
    "\n",
    "\n",
    "# 模型初始化\n",
    "vgg_conv_layers = [64, 64, 'pooling', 128, 128, 'pooling', 256, 256, 256, 'pooling', 512, 512, 512, 'pooling', 512, 512, 512, 'pooling']\n",
    "\n",
    "# 初始通道—— 三通道\n",
    "vgg16 = VGG(make_layers(vgg_conv_layers, in_channels=3))\n",
    "\n",
    "# 是否使用GPU\n",
    "if use_gpu:\n",
    "    vgg16 = vgg16.cuda()\n",
    "    \n",
    "# loss and optimizer\n",
    "loss_fn = nn.CrossEntropyLoss()\n",
    "\n",
    "optimizer = torch.optim.Adam(vgg16.parameters(), lr=learning_rate)\n",
    "\n",
    "# Training\n",
    "vgg16.train()\n",
    "\n",
    "for epoch in range(EPOCH):\n",
    "    total = 0\n",
    "    correct = 0\n",
    "    for i, (images, labels) in enumerate(train_loader):\n",
    "        images = Variable(images)\n",
    "        labels = Variable(labels)\n",
    "        \n",
    "        if use_gpu:\n",
    "            images = images.cuda()\n",
    "            labels = labels.cuda()\n",
    "        \n",
    "        # forward + backward + optimize\n",
    "        optimizer.zero_grad()\n",
    "        y_pred = vgg16(images)\n",
    "\n",
    "        loss = loss_fn(y_pred, labels)\n",
    "\n",
    "        loss.backward()\n",
    "\n",
    "        optimizer.step()\n",
    "\n",
    "        if (i + 1) % 100 == 0:\n",
    "            print(\"Epoch [%d/%d], Iter [%d/%d] Loss: %.4f\" % (epoch + 1, EPOCH, i + 1, 200, loss.data[0]))\n",
    "\n",
    "        # 计算训练精确度\n",
    "        _, predicted = torch.max(y_pred.data, 1)\n",
    "        total += labels.size(0)\n",
    "        correct += (predicted == labels.data).sum()\n",
    "    \n",
    "    # 结束一次迭代\n",
    "    print('Accuracy of the model on the train images: %d %%' % (100 * correct / total))\n",
    "    \n",
    "    # Decaying Learning Rate\n",
    "    if (epoch+1) % 2 == 0:\n",
    "        learning_rate /= 3\n",
    "        optimizer = torch.optim.Adam(vgg16.parameters(), lr=learning_rate)\n",
    "\n",
    "\n",
    "# Test\n",
    "vgg16.eval()\n",
    "\n",
    "correct = 0\n",
    "total = 0\n",
    "\n",
    "for images, labels in test_loader:\n",
    "    images = Variable(images)\n",
    "    labels = Variable(labels)\n",
    "    if use_gpu:\n",
    "        images = images.cuda()\n",
    "        labels = labels.cuda()\n",
    "    y_pred = vgg16(images)\n",
    "    _, predicted = torch.max(y_pred.data, 1)\n",
    "    total += labels.size(0)\n",
    "    temp = (predicted == labels.data).sum()\n",
    "    correct += temp\n",
    "\n",
    "print('Accuracy of the model on the test images: %d %%' % (100 * correct / total))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在我自己电脑上跑出来的结果是：\n",
    "\n",
    "```\n",
    "Epoch [1/5], Iter [100/200] Loss: 2.3071\n",
    "Accuracy of the model on the train images: 12 %\n",
    "Epoch [2/5], Iter [100/200] Loss: 1.8102\n",
    "Accuracy of the model on the train images: 26 %\n",
    "Epoch [3/5], Iter [100/200] Loss: 1.6054\n",
    "Accuracy of the model on the train images: 38 %\n",
    "Epoch [4/5], Iter [100/200] Loss: 1.4914\n",
    "Accuracy of the model on the train images: 43 %\n",
    "Epoch [5/5], Iter [100/200] Loss: 1.3892\n",
    "Accuracy of the model on the train images: 49 %\n",
    "Accuracy of the model on the test images: 49 %\n",
    "\n",
    "```\n",
    "\n",
    "看上去似乎还没Alexnet强，其实正常...因为我们模型的参数便多了，但是训练次数却变多，很多参数得不到充分的训练"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.2"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
